package com.book.oauth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.annotation.Resource;
import java.util.Arrays;

/**
 * Description： 指定认证中心，指定客户端
 * 指定token的存储方式等。
 * @Author： leo.xiong
 * @CreateDate： 2019/10/11 15:38
 * @Email： leo.xiong@suyun360.com
 * Version：1.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    public static final String JWT_ACCESS_TOKEN = "@#JWTABCDEFGHIJKLMNOPQRSTUVWXYZLEOBOOK123456789/*";
    /**
     * 注入authenticationManager 来支持 password grant type
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Resource(name = "clientDetailsServiceImpl")
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationServerEndpointsConfiguration endpoints;

    /**
     * Map encoders = Maps.newHashMap();
     * encoders.put(EncodeUtils.MD5, new MessageDigestPasswordEncoder(EncodeUtils.MD5));
     * PasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(EncodeUtils.MD5, encoders);
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 对Jwt签名时，增加一个密钥
     * JwtAccessTokenConverter：对Jwt来进行编码以及解码的类
     * 非对称加密方式(jks文件可能过期，jks文件需要Java keytool工具生成)
     * KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
     * jwtAccessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        //对称加密方式
        jwtAccessTokenConverter.setSigningKey(JWT_ACCESS_TOKEN);
        return jwtAccessTokenConverter;
    }

    /**
     * 注入自定义token生成方式（jwt）
     *
     * @return
     */
    @Bean
    public TokenEnhancer myTokenEnhancer() {
        CustomTokenEnhancer customTokenEnhancer = new CustomTokenEnhancer();
        customTokenEnhancer.setSecretKeySpec(JWT_ACCESS_TOKEN);
        return customTokenEnhancer;
    }

    /**
     * token保存在内存中（也可以保存在数据库、Redis中）。
     * 如果保存在中间件（数据库、Redis），那么资源服务器与认证服务器可以不在同一个工程中。
     * 注意：如果不保存access_token，则没法通过access_token取得用户信息
     * 存内存
     * new InMemoryTokenStore();
     * 存redis
     * new RedisTokenStore(redisConnectionFactory);
     * jwt存储
     * new JwtTokenStore(jwtAccessTokenConverter());
     * 存数据库
     * new JdbcTokenStore(dataSource);
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        JacksonSerializationStrategy jacksonSerializationStrategy = new JacksonSerializationStrategy();
        jacksonSerializationStrategy.setClientDetailsService(clientDetailsService);
        redisTokenStore.setSerializationStrategy(jacksonSerializationStrategy);
        return redisTokenStore;
    }

    /**
     * 配置授权服务器端点
     * DefaultTokenServices tokenServices = (DefaultTokenServices) endpoints.getDefaultAuthorizationServerTokenServices();
     * tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1));
     *
     * @param endpoints
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        //指定认证管理器
        endpoints.authenticationManager(authenticationManager);
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        //指定token存储位置
        endpoints.tokenStore(tokenStore());

        // 自定义jwt生成token方式(不用这种方式可以注释)
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(myTokenEnhancer(), jwtAccessTokenConverter()));
        endpoints.tokenEnhancer(tokenEnhancerChain);
        // 配置TokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        // 这里如果设置为false则不能更新refresh_token，如果需要刷新token的功能需要设置成true
        tokenServices.setSupportRefreshToken(true);
        // 设置上次RefreshToken是否还可以使用 默认为true
        tokenServices.setReuseRefreshToken(false);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        endpoints.tokenServices(tokenServices);
    }

    @Bean
    public ResourceServerTokenServices resourceServerTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetailsService);
        return tokenServices;
    }

    @Bean
    public AuthorizationServerTokenServices authorizationServerTokenServices() {
        return endpoints.getEndpointsConfigurer().getTokenServices();
    }

    /**
     * 配置客户端详情信息，客户端详情信息在这里进行初始化，通过数据库来存储调取详情信息
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        oauthServer
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }
}

